/*
 * Project it.battlehorse.rcp.tools
 * Created on Nov 18, 2005
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * A copy of the LGPL is available also at
 * http://www.gnu.org/copyleft/lesser.html
 * 
 */ 
package it.battlehorse.rcp.tools.actors.impl;

import it.battlehorse.rcp.sl.annot.Injected;
import it.battlehorse.rcp.sl.annot.Serviceable;
import it.battlehorse.rcp.tools.Activator;
import it.battlehorse.rcp.tools.actors.ActorDescriptor;
import it.battlehorse.rcp.tools.actors.ActorRunnerEvent;
import it.battlehorse.rcp.tools.actors.IActor;
import it.battlehorse.rcp.tools.actors.IActorConstants;
import it.battlehorse.rcp.tools.actors.IActorRegistry;
import it.battlehorse.rcp.tools.actors.IActorRunnable;
import it.battlehorse.rcp.tools.actors.IActorRunner;
import it.battlehorse.rcp.tools.actors.IActorRunnerListener;
import it.battlehorse.rcp.tools.commons.Severity;
import it.battlehorse.rcp.tools.commons.actors.BasicValidationStatus;
import it.battlehorse.rcp.tools.commons.actors.IActorExecutionStatus;
import it.battlehorse.rcp.tools.commons.actors.IValidationStatus;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PlatformUI;

/**
 * Provides a default implementation for the {@link IActorRunner} interface. This implementation adopts
 * the singleton pattern so that only a single instance of the class is available to the
 * platform. 
 * 
 * @author battlehorse
 * @since Nov 18, 2005
 */
@Serviceable
public class ActorRunner implements IActorConstants, IActorRunner {
	
	/**
	 * The list of registered listeners
	 */
	private List<IActorRunnerListener> listenersList;
	
	/**
	 * Contains the shared instance of the runner
	 */
	private static ActorRunner instance;
	
	/**
	 * A reference to the actor registry
	 */
	private IActorRegistry registry;
	
	/**
	 * Returns the shared instance of the class. This method should not be invoked
	 * directly : the runner should be accessed using the Service locator framework.
	 * 
	 * @return the shared actor runner instance
	 */
	public static synchronized ActorRunner getInstance() {
		if (instance == null)
			instance =  new ActorRunner();
		
		return instance;
	}	
	
	/**
	 * Injects a reference to the actor registry
	 * 
	 * @param registry a valid actor registry
	 */
	@Injected
	public void setActorRegistry(IActorRegistry registry) {
		this.registry = registry;
	}
	
	/*
	 * (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#newButtonActorLauncher(java.lang.String)
	 */
	public SelectionListener newButtonActorLauncher(String actorId) {
		return new ActorLauncherListener(actorId);
	}
	
	/*
	 * (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#newKeyActorLauncher(java.lang.String, char, int)
	 */
	public KeyListener newKeyActorLauncher(String actorId, char c, int stateMask) {
		return new ActorLauncherKeyListener(actorId, c , stateMask);
	}
	
	/*
	 * (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#getActionActorLauncher(java.lang.String)
	 */
	public IAction getActionActorLauncher(String actorId) {
		return new ActorLauncherAction(actorId);
	}
	
	/**
	 * Creates a new instance of the class
	 *
	 */
	private ActorRunner() {
		listenersList = new ArrayList<IActorRunnerListener>();
		
		addRunnerListener(new VisualActorRunnerListener());
		addRunnerListener(new LogActorRunnerListener());
	}

	/* (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#runJob(java.lang.String)
	 */
	public void runJob(String actorId) {		
		IActor actor = null;
		ActorDescriptor actorConf = registry.getActorForActorId(actorId);
		
		if (actorConf != null)
			try {
				actor = actorConf.getActorInstance();
			}
			catch(CoreException ce) {
				ActorRunnerEvent e = new ActorRunnerEvent(actorId, actorConf.getName(), ce);
				notifyRunnerListeners(e);					
				ce.printStackTrace();				
			}
				
		if (actor != null && actorConf != null) {
			
			//
			// Extracting actor name
			//
			String actorName = actorConf.getName();			
			
			//
			// Actor validation
			//
			IViewPart v = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().
								findView(actorConf.getViewId());
			
			IValidationStatus valStatus = actor.validate(v);
			
			if (valStatus == null) {
				// Unimplemented actor
				valStatus = new BasicValidationStatus(
						"Unimplemented actor \"" + actorName + "\"",
						false,
						null,
						Severity.WARNING);
			}
			
			if (valStatus.isValid()) {
				
				IActorRunnable runnable = actor.getRunnable(v);
				try {
					PlatformUI.getWorkbench().getProgressService().busyCursorWhile(runnable);
				}
				catch(InterruptedException ioe) {
					
					ActorRunnerEvent e = new ActorRunnerEvent(actorId, actorName, ioe);
					notifyRunnerListeners(e);					
					ioe.printStackTrace();
				}
				catch(InvocationTargetException ite) {
					
					ActorRunnerEvent e = new ActorRunnerEvent(actorId, actorName, ite);
					notifyRunnerListeners(e);
					ite.printStackTrace();
				}
				
				IActorExecutionStatus<?> execStatus = runnable.getExecutionStatus();
				
				ActorRunnerEvent e = new ActorRunnerEvent(actorId, actorName, execStatus);
				notifyRunnerListeners(e);											
			}
			else {
				
				ActorRunnerEvent e = new ActorRunnerEvent(actorId, actorName, valStatus);
				notifyRunnerListeners(e);				
			}
			
		}
	}
	
	/* (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#addRunnerListener(it.battlehorse.rcp.tools.actors.IActorRunnerListener)
	 */
	public void addRunnerListener(IActorRunnerListener listener) {				

			listenersList.add(listener);

	}

	/**
	 * Notifies the registered listeners
	 * @param event
	 */
	void notifyRunnerListeners(ActorRunnerEvent event) {

			for (IActorRunnerListener listener  : listenersList) {
				listener.runnerEventOccurred(event);
			}

	}

	/* (non-Javadoc)
	 * @see it.battlehorse.rcp.tools.actors.IActorRunner#removeRunnerListener(it.battlehorse.rcp.tools.actors.IActorRunnerListener)
	 */
	public void removeRunnerListener(IActorRunnerListener listener) {

			listenersList.remove(listener);

	}

	/**
	 * A basic listener which is used to attach a behaviour to SWT buttons so that
	 * they can launch actors. 
	 */
	@Serviceable
	public class ActorLauncherListener extends SelectionAdapter {
		
		/**
		 * The id of the actor this listener will invoke
		 */
		private String actorId;
		
		/**
		 * A reference to a valid Actor runner
		 */
		private IActorRunner runner;
		
		/**
		 * Injects a reference to a valid actor runner
		 * 
		 * @param runner an actor runner
		 */
		@Injected
		public void setActorRunner(IActorRunner runner) {
			this.runner = runner;
		}
		
		/**
		 * Creates a new instance of the class bound to a given actor.
		 * 
		 * @param actorId the id of the actor this listener will be bound to
		 */
		public ActorLauncherListener(String actorId) {
			this.actorId = actorId;
		}
		
		@Override
		public void widgetSelected(SelectionEvent e) {
			runner.runJob(actorId);
		}
	}
	
	/**
	 * A basic {@code Action} implementation which runs the actor whose id is passed
	 * as parameter
	 */
	@Serviceable
	public class ActorLauncherAction extends Action {
		
		/* The id of the actor this action will invoke */
		private String actorId;
		
		/* A reference to a valid Actor runner */
		private IActorRunner runner;
		
		/* A reference to a valid Actor registry */
		private IActorRegistry registry;
		
		/**
		 * Injects a reference to a valid actor runner and a valid registry
		 * 
		 * @param runner an actor runner
		 * @param registry an actor registry
		 */
		@Injected
		public void setActorComponents(IActorRunner runner, IActorRegistry registry) {
			this.runner = runner;
			this.registry = registry;
		}		
		
		/**
		 * Creates a new instance of the class bound to a given actor
		 * 
		 * @param actorId the id of the actor this action will invoke
		 */
		public ActorLauncherAction(String actorId) {
			super();
			
			String name = registry.getActorForActorId(actorId).getName();
			super.setText("Run \"" + name + "\"");
			super.setToolTipText("Runs the \"" + name + "\" actor");		
			super.setImageDescriptor(Activator.getImageDescriptor("resources/actor/run.png"));
			
			this.actorId = actorId;
		}
		
		@Override
		public void run() {
			runner.runJob(actorId);
		}
	}
	
	/**
	 * A basic listener which is used to attach a behaviour to key events which
	 * occur on a given widget  so that they can launch actors. 
	 * 
	 */
	@Serviceable
	public class ActorLauncherKeyListener extends KeyAdapter {
				
		/* The id of the actor this listener will invoke */
		private String actorId;
		
		/* The character this listener will respond to */
		private char c;
		
		/* The state mask that must be matched in order to activate this listener */
		private int stateMask;
		
		
		/* A reference to a valid Actor runner */
		private IActorRunner runner;
		
		/**
		 * Injects a reference to a valid actor runner
		 * 
		 * @param runner an actor runner
		 */
		@Injected
		public void setActorRunner(IActorRunner runner) {
			this.runner = runner;
		}		
		
		/**
		 * Creates a new instance of the class bound to a given actor.
		 * 
		 * @param actorId the id of the actor this listener will be bound to
		 */
		public ActorLauncherKeyListener(String actorId, char c, int stateMask) {
			this.actorId = actorId;
			this.c = c;
			this.stateMask = stateMask;
		}		
		
		@Override
		public void keyReleased(KeyEvent e) {
			if (e.character  == c && e.stateMask == stateMask)
				runner.runJob(actorId);
		}
	}
	

}
